package com.xaicode.learn.nio.groupchat;

import lombok.extern.slf4j.Slf4j;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.util.Iterator;

/**
 * @author Locker xaicode@sina.com
 * @version 1.0
 * @date 2021/6/13 0013 19:42
 */
@Slf4j
public class GroupChatServer {

    private Selector selector;
    private ServerSocketChannel listenChannel;
    private static final Integer PORT = 6667;

    public GroupChatServer() {
        try {
            selector = Selector.open();
            listenChannel = ServerSocketChannel.open();
            // bind port
            listenChannel.socket().bind(new InetSocketAddress(PORT));
            listenChannel.configureBlocking(false);
            // bind channel to selector
            listenChannel.register(selector, SelectionKey.OP_ACCEPT);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public void listen() {
        try {
            while (true) {
//                final int select = selector.select(SEC_1 * 2);
                final int select = selector.select();
                if (select == 0) {
                    log.info("there is no event in 1s, continue listen");
                    continue;
                }
                // new event
                final Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
                while (iterator.hasNext()) {
                    // get selection key
                    final SelectionKey selectionKey = iterator.next();
                    // OP_ACCEPT
                    if (selectionKey.isAcceptable()) {
                        SocketChannel socketChannel = listenChannel.accept();
                        // register to selector
                        socketChannel.configureBlocking(false);
                        socketChannel.register(selector, SelectionKey.OP_READ);
                        // online
                        log.info("[{}] is online", socketChannel.getRemoteAddress());
                    }
                    // OP_READ
                    if (selectionKey.isReadable()) {
                        SocketChannel channel = null;
                        try {
                            channel = (SocketChannel) selectionKey.channel();
                            ByteBuffer buffer = ByteBuffer.allocate(1024);
                            int read = channel.read(buffer);
                            if (read > 0) {
                                // read content
                                String content = new String(buffer.array());
                                log.info("[{}] read - {}", channel.getRemoteAddress(), content);
                                // send to other channel(client), exclude self
                                sendToOtherChannel(channel, content);
                            }
                        } catch (IOException e) {
                            try {
                                log.info("client [{}] is down", channel.getRemoteAddress(), e);
                                // cancel register
                                selectionKey.cancel();
                                // close channel
                                channel.close();
                            } catch (Exception e2) {
                                e.printStackTrace();
                            }
                        }
                    }

                    // remove this selection key for preventing repetitive operation
                    iterator.remove();
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private void sendToOtherChannel(SocketChannel sendChannel, String content) throws IOException {
        log.info("send content to other client");
        for (SelectionKey selectionKey : selector.keys()) {
            // get channel by selection-key
            Channel targetChannel = selectionKey.channel();
            // exclude self
            if (!(targetChannel instanceof SocketChannel) || targetChannel.equals(sendChannel)) {
                continue;
            }
            // send
            SocketChannel destChannel = (SocketChannel) targetChannel;
            ByteBuffer buffer = ByteBuffer.wrap(content.getBytes());
            destChannel.write(buffer);

            log.info("send content to [{}]", destChannel.getRemoteAddress());
        }
    }

    public static void main(String[] args) {
        final GroupChatServer server = new GroupChatServer();
        server.listen();
    }

}
